using System;
using System.Xml;
using System.Drawing;
using System.Collections.Generic;
using Clandestine.Graphics;

namespace Clandestine.Engines.Field
{
	public delegate void TileChangedHandler(int layerDepth, int x, int y); // to allow updating of sprite!
	public delegate void VisibilityChangedHandler(); // so we can set dirty
	
	// eg. Floor layers are a group -- allows us to have one megaquad for a section of a map with multiple layers.
	public abstract class LayerGroup : IWatchable
	{
		public List<Layer> Layers = new List<Layer>();
		public RenderList RenderList { get; private set; }
		public Clandestine.Graphics.Graphics Graphics { get; private set; }
		
		public Map Map { get; private set; }
		public Size Size { get { return new Size(MaxX - MinX, MaxY - MinY); } }
		public object Parent { get; private set; } // the Level/Object that owns this LayerGroup -- for Slick purposes! :)
		
		public abstract int Depth { get; set; }
		
		public event Changed Changed;
		
		private bool dirtyMinMax = true;
		
		private bool dirty;
		public bool Dirty // if tiles have been changed and sprite[s] hasn't been updated.
		{ 
			get { return dirty; }
			set 
			{ 
				dirty = value;
				if (value)
				{
					dirtyMinMax = true;
					if (Changed != null)
						Changed(this);
				}
			}
		}
		
		private bool visible = true;
		public bool Visible
		{
			get { return visible; } 
			set
			{
				visible = value;
				
				foreach (Layer l in Layers)
					l.Visible = value; // they'll take care of making us dirty
			}
		}
		
		public Point position;
		public virtual Point Position 
		{
			get { return position; } 
			set
			{
				position = value;
				this.Dirty = true;
			}
		}
		
		private void recalculateBoundsIfNecessary()
		{
			if (dirtyMinMax)
			{
				minx = miny = int.MaxValue;
				maxx = maxy = int.MinValue;
				
				foreach (Layer l in Layers)
				{
					miny = (l.MinY < miny ? l.MinY : miny);
					minx = (l.MinX < minx ? l.MinX : minx);
					maxy = (l.MaxY > maxy ? l.MaxY : maxy);
					maxx = (l.MaxX > maxx ? l.MaxX : maxx);
				}
				
				dirtyMinMax = false;
			}
		}
		
		private int miny;
		public int MinY { get { recalculateBoundsIfNecessary(); return miny; } }
		
		private int minx;
		public int MinX { get { recalculateBoundsIfNecessary(); return minx; } }
		
		private int maxy;
		public int MaxY { get { recalculateBoundsIfNecessary(); return maxy; } }
		
		private int maxx;
		public int MaxX { get { recalculateBoundsIfNecessary(); return maxx; } }
		
		public LayerGroup(RenderList r, Map map, object parent)
		{
			this.Graphics = r.Graphics;
			this.RenderList = r;
			this.Map = map;
			this.Parent = parent;
		}
		
		public abstract List<Renderable> GetRenderables();
		public abstract void RedrawDirty();
		
		// Positions the top left tile at the given point.
		// LayerGroup.Position doesnt, as it is relative to some offset
		// However, we do need to know the minimum tile x and minimum tile y of the object/level's other 
		// layer groups so that we can calculate what our offset is.
		public void PositionTopLeft(Point pos, int minx, int miny)
		{
			int offsetX = -1 * minx;
			int offsetY = -1 * miny;
			
			this.Position = new Point(pos.X + offsetX, pos.Y + offsetY);
		}

		public void AddLayer(Layer l)
		{
			lock (Layers)
				this.AddLayer(l, this.Layers.Count); // add on 'top' of depth stack
		}
		public void AddLayer(Layer l, int depth)
		{
			lock (Layers)
			{
				Layers.Insert(depth, l);
				Layers[depth].TileChanged += OnTileChange;
				Layers[depth].VisibilityChanged += OnVisibilityChange;
				Layers[depth].ContainingGroup = this;
			}
			Dirty = true;
		}
		
		public Layer RemoveLayer(int depth)
		{
			lock (Layers)
			{
				Layer l = this.Layers[depth];
				this.Layers.Remove(l);
				
				l.TileChanged -= OnTileChange;
				l.VisibilityChanged -= OnVisibilityChange;
				l.ContainingGroup = null;
				
				Dirty = true;
				return l;
			}
		}
		
		public void Save(XmlWriter xml, string elementName)
		{
			xml.WriteStartElement(elementName);
			
			// Layers
			foreach (Layer l in Layers)
				l.Save(xml);
			
			xml.WriteEndElement();
		}
		
		public void Load(XmlReader xml, string endElement, Point position, Dictionary<int, int> remapping)
		{
			// check for <floor/> etc.
			if ((xml.Name == endElement) && (xml.IsEmptyElement))
				return;
		
			while (xml.Read())
			{
				if (xml.NodeType == XmlNodeType.Element)
                {
                    switch (xml.Name)
                    {
                    	case "layer":
                    		Layer l = new Layer(this);
                    		l.Load(xml, remapping);
                    		this.AddLayer(l);	
                    		break;
                    }
                }
                else if ((xml.NodeType == XmlNodeType.EndElement) && (xml.Name == endElement))
                	return;
             }
		}
		
		public IEnumerable<Spritesheet> GetSpritesheetsUsed()
		{
			foreach (Layer l in Layers)
				foreach (KeyValuePair<Spritesheet, int> kvp in l.SpritesheetRefCount)
					if (kvp.Value > 0)
						yield return kvp.Key;
		}
		
		public virtual void Dispose()
		{
			// plz override me
		}
		
		protected virtual void OnTileChange(int layerDepth, int x, int y)
		{
		}
		
		protected virtual void OnVisibilityChange()
		{
		}
	}
}

